﻿namespace IronLua.Runtime

#nowarn "9"

open System
open System.Globalization
open System.Collections.Generic
open System.Runtime.InteropServices
open IronLua
open IronLua.Parsing
open IronLua.Runtime

type LuaValueTypes = 
    | None = -1
    | Nil = 0
    | Boolean = 1
    | LightUserData = 2
    | Number = 3
    | String = 4
    | Table = 5
    | Function = 6 
    | UserData = 7
    | Thread = 8
    | Prototype = 9
    | Upvalue = 10
    | DeadKey = 11
    | LightClrFunction = 16
    | ClrClosure = 26

and internal ILuaDynamicObject =
    interface
        abstract member Metatable : LuaValue with get, set
    end

and internal ITable =
    interface 
        inherit ILuaDynamicObject

    end

and internal UserData(value : obj) =
    class
        interface ILuaDynamicObject with
            member val Metatable : LuaValue = LuaValue.Nil with get, set

        member val Object : obj = value with get       
    end

and ClrFunc<'a> = System.Func<'a>

and ClrFunc<'a, 'b> = System.Func<'a, 'b>

and ClrFunc<'a, 'b, 'c> = System.Func<'a, 'b, 'c>

and internal Func = delegate of Thread * IClosure -> unit

and [<AllowNullLiteral>] internal IFuncMetadata =
    interface
        abstract member Id : Guid
        abstract member Name : string
        abstract member SyntaxTree : Ast.Expr
        abstract member Variables : Map<Ast.Symbol, Ast.Expr>
        abstract member IsVarArgs : bool
        abstract member Call : Func
        abstract member CallDebug : Func
    end

and internal FuncMetadata(id: Guid, 
                          name: string, 
                          ast: Ast.Expr, 
                          vars : Map<Ast.Symbol, Ast.Expr>, 
                          isvarargs: bool, 
                          call: Func, 
                          calldebug: Func) =
    class
        let mutable call = call
        let mutable calldebug = calldebug

        interface IFuncMetadata with
            member val Id = id with get
            member val Name = name with get
            member val SyntaxTree = ast with get
            member val Variables = vars with get
            member val IsVarArgs = isvarargs with get
            member val Call = call with get
            member val CallDebug = calldebug with get
    end

and [<AllowNullLiteral>] internal IStorage =
    interface
        abstract member Locals : LuaValue array
        abstract member Upvalues : LuaValue array
    end

and [<AllowNullLiteral>] internal IClosure = 
    interface
        inherit IStorage
        abstract member Thread : Thread
        abstract member Id : Guid
        abstract member Metadata : IFuncMetadata
        abstract member IsOptimized : bool
        abstract member IsDebugEnabled : bool with get, set
        abstract member LuaInvoke : unit -> unit
        abstract member ClrInvoke : obj seq -> obj seq
    end

and ClrThread = System.Threading.Thread

and internal CallInfo(closure : IClosure) =
    struct
        member this.Closure = closure
        member this.Name = closure.Metadata.Name
    end

and [<AllowNullLiteral>] internal Thread(state : LuaState, _env : LuaValue) =
    class
        let mutable callstack = Array.zeroCreate 128
        let mutable stack = Array.zeroCreate 128
        let mutable lastci = -1
        let mutable lastpi = -1
        let mutable defaultErrHandler : IClosure = null
        let mutable errHandler : IClosure = null
        
        member val State = state with get
        member val Environ = _env with get

        member val CallStack : CallInfo array = callstack with get
        member val ValueStack : LuaValue array = stack with get
        member val DefaultErrorHandler = defaultErrHandler with get, set
        member val ErrorHandler = errHandler with get, set

        member this.PushParameter parm = failwith "pushparam"
        member this.PushParameters parms = parms |> Seq.iter this.PushParameter
        member this.PushResult value = failwith "pushresult"
        member this.PushResults values = values |> Seq.iter this.PushResult
        member this.PopResults() : LuaValue seq = failwith "popresults"
        member this.CleanUpOnFuncException() = failwith "CleanUpOnFuncException"

        member public this.EnterFrame ci =
            if lastci >= callstack.Length then Array.Resize(ref callstack, callstack.Length + 16)
            lastci <- lastci + 1
            callstack.[lastci] <- ci
       
        member public this.ExitFrame() =
            if lastci < 0 
            then LuaRuntimeException("no frames on the callstack") |> raise
            else let ci = callstack.[lastci]
                 lastci <- lastci - 1
                 ci

    end

and [<NoComparison; StructLayout(LayoutKind.Explicit)>] 
    internal LuaValue = 
    struct              

        static member ToClrValue (v : LuaValue) : obj =
            let t = v.Type
            match t with
            | LuaValueTypes.Boolean ->  box v.Bool
            | LuaValueTypes.String ->   box v.String
            | LuaValueTypes.Number ->   box v.Number
            | LuaValueTypes.Nil ->      null
            | _ -> LuaRuntimeException(sprintf "unable to convert %s." <| t.ToString()) |> raise    

        static member FromClrValue (v : obj) : LuaValue =
            match v with
            | null ->               LuaValue.Nil
            | :? bool as b ->       LuaValue.FromBool(b)
            | :? string as s ->     LuaValue.FromString(s)
            | :? double as d ->     LuaValue.FromDouble(d)
            | :? single as s ->     LuaValue.FromSingle(s)
            | _ ->                  LuaRuntimeException(sprintf "unable to convert %s." <| v.ToString()) |> raise    

        static member ToClrValues values = 
            values |> Seq.map LuaValue.ToClrValue

        static member FromClrValues values = 
            values |> Seq.map LuaValue.FromClrValue

        static member Nil : LuaValue = LuaValue()

        static member FromBool (b : bool) = 
            let mutable v = LuaValue()                           
            v.TypeTag <- LuaValueTypes.Boolean |> int
            v.Bool <- b
            v

        static member FromSingle (s : single) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- s |> double 
            v

        static member FromDouble (d : double) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- d
            v

        static member FromInt32 (i : int) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- i |> double
            v

        static member FromUInt32 (ui : uint32) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- ui |> double
            v

        static member FromInt64 (i : int64) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- i |> double
            v

        static member FromUInt64 (ui : uint64) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.Number |> int
            v.Number <- ui |> double
            v

        static member FromString (s : string) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.String |> int
            v.String <- s
            v

        static member FromUserData(o : obj) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.UserData |> int
            v.UserData <- new UserData(o)
            v

        static member FromObject(o : obj) = 
            let mutable v = LuaValue()
            v.TypeTag <- LuaValueTypes.LightUserData |> int
            v.ClrObject <- o
            v

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable String : string

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable Func : IClosure

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable Table : ITable

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable Coroutine : Thread

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable UserData : UserData

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable LightUserData : obj

        [<FieldOffset(0); DefaultValue(false)>]
        val mutable ClrObject : obj

        [<FieldOffset(8); DefaultValue(false)>]
        val mutable Bool : bool

        [<FieldOffset(8); DefaultValue(false)>]
        val mutable Number : double

        [<FieldOffset(16); DefaultValue(false)>]
        val mutable TypeTag : int

        member this.Type with get() : LuaValueTypes = this.TypeTag |> enum

        override this.ToString() = 
            match this.Type with
            | LuaValueTypes.Boolean ->           sprintf "%b (bool)" this.Bool
            | LuaValueTypes.ClrClosure ->        sprintf "%A (clr closure)" this.Func
            | LuaValueTypes.DeadKey ->           "(dead key)"
            | LuaValueTypes.Function 
            | LuaValueTypes.LightClrFunction ->  sprintf "%A (func)" this.Func
            | LuaValueTypes.LightUserData ->     sprintf "%A (light userdata)" this.LightUserData
            | LuaValueTypes.Nil ->               "nil"
            | LuaValueTypes.None ->              "none"
            | LuaValueTypes.Number ->            sprintf "%f (number)" this.Number
            | LuaValueTypes.Prototype ->         sprintf "%A (proto)" this.LightUserData
            | LuaValueTypes.String ->            sprintf "%s (string)" this.String
            | LuaValueTypes.Table ->             sprintf "%A (table)" this.Table
            | LuaValueTypes.Thread ->            sprintf "%A (thread)" this.Coroutine
            | LuaValueTypes.UserData ->          sprintf "%A (userdata)" this.UserData
            | LuaValueTypes.Upvalue ->           sprintf "%A (upvalue)" this.ClrObject
            | _ ->                              "unknown"

    end